﻿// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号：程序员速成 ，内含一辈子都让你感激自己的优质视频教程，欢迎关注

#include <iostream>
#include <list>
#include <map>

#ifdef _DEBUG   //只在Debug（调试）模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif

//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996) 

namespace _nmsp1
{
	class Fighter; //类前向声明
	list<Fighter*> g_playerList; 

	//玩家父类（以往的战斗者类）
	class Fighter
	{
	public: 
		Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
		{
			m_iFamilyID = -1; //-1表示没有加入任何家族
		}
		virtual ~Fighter() {} //析构函数

	public:
		void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID
		{
			m_iFamilyID = tmpID;
		}

	public:
		void SayWords(string tmpContent) //玩家说了某句话
		{
			if (m_iFamilyID != -1)
			{
				//该玩家属于某个家族，应该把聊天内容信息传送给该家族的其他玩家
				for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter)
				{
					if (m_iFamilyID == (*iter)->m_iFamilyID)
					{
						//同一个家族的其他玩家也应该收到聊天信息
						NotifyWords((*iter), tmpContent);
					}
				}
			}
		}
	private:
		void NotifyWords(Fighter* otherPlayer, string tmpContent) //其他玩家收到了当前玩家的聊天信息
		{
			//显示信息
			cout << "玩家：" << otherPlayer->m_sPlayerName << "收到了玩家：" << m_sPlayerName << " 发送的聊天信息：" << tmpContent << endl;
		}

	private:
		int m_iPlayerID;  //玩家ID，全局唯一
		string m_sPlayerName; //玩家名字
		int m_iFamilyID;  //家族ID
	};

	//"战士"类玩家，父类为Fighter
	class F_Warrior :public Fighter
	{
	public:
		F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
	};

	//"法师"类玩家，父类为Fighter
	class F_Mage :public Fighter
	{
	public:
		F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
	};

}

namespace _nmsp2
{
	class Fighter; //类前向声明
	class Notifier //通知器父类
	{
	public:
		virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加入到列表中
		virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除
		virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息
		virtual ~Notifier() {}
	};

	//玩家父类
	class Fighter
	{
	public:
		Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
		{
			m_iFamilyID = -1; //-1表示没有加入任何家族
		}
		virtual ~Fighter() {} //析构函数

	public:
		void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID
		{
			m_iFamilyID = tmpID;
		}
		int GetFamilyID() //获取家族ID
		{
			return m_iFamilyID;
		}

	public:
		void SayWords(string tmpContent,Notifier *notifier) //玩家说了某句话
		{
			notifier->notify(this, tmpContent);
		}
	
		//通知该玩家接收到其他玩家发送来的聊天信息，虚函数，子类可以覆盖以实现不同的功能
		virtual void NotifyWords(Fighter* talker, string tmpContent)
		{
			//显示信息
			cout << "玩家：" << m_sPlayerName << "收到了玩家：" << talker->m_sPlayerName << " 发送的聊天信息：" << tmpContent << endl;
		}

	private:
		int m_iPlayerID;  //玩家ID，全局唯一
		string m_sPlayerName; //玩家名字
		int m_iFamilyID;  //家族ID
	};

	//"战士"类玩家，父类为Fighter
	class F_Warrior :public Fighter
	{
	public:
		F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
	};

	//"法师"类玩家，父类为Fighter
	class F_Mage :public Fighter
	{
	public:
		F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
	};

	//聊天信息通知器
	class TalkNotifier :public Notifier
	{
	public:
		//将玩家增加到家族列表中来
		virtual void addToList(Fighter* player)
		{
			int tmpfamilyid = player->GetFamilyID();
			if (tmpfamilyid != -1) //加入了某个家族
			{
				auto iter = m_familyList.find(tmpfamilyid);
				if (iter != m_familyList.end())
				{
					//该家族id在map中已经存在
					iter->second.push_back(player); //直接把该玩家加入到该家族
				}
				else
				{
					//该家族id在map中不存在
					list<Fighter*> tmpplayerlist;
					m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist)); //以该家族id为key，增加条目到map中
					m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家
				}
			}
		}
		//将玩家从家族列表中删除
		virtual void removeFromList(Fighter* player)
		{
			int tmpfamilyid = player->GetFamilyID();
			if (tmpfamilyid != -1) //加入了某个家族
			{
				auto iter = m_familyList.find(tmpfamilyid);
				if (iter != m_familyList.end())
				{
					m_familyList[tmpfamilyid].remove(player);
				}
			}
		}

		//家族中某玩家说了句话，调用该函数来通知家族中所有人
		virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家
		{
			int tmpfamilyid = talker->GetFamilyID();
			if (tmpfamilyid != -1) //加入了某个家族
			{
				auto itermap = m_familyList.find(tmpfamilyid);
				if (itermap != m_familyList.end())
				{
					//遍历该玩家所属家族的所有成员
					for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist)
					{
						(*iterlist)->NotifyWords(talker, tmpContent);
					}
				}
			}
		}

	private:
		//map中的key表示家族id，value代表该家族中所有玩家列表
		map<int, list<Fighter*> > m_familyList;
	};
}


int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口

	//第5章 观察者（Observer）模式
	//（1）一个遍历问题导致的低效率范例
	//（2）引入观察者（Observer）模式
	 //观察者设计模式 定义（实现意图）：定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于
	    //它的对象都会自动得到通知。
	//发布-订阅(Publish-Subscribe）；

	//观察者模式的四种角色
	//a)Subject（主题）：观察目标，这里指Notifier类。
	//b)ConcreteSubject（具体主题）：这里指TalkNotifier类。
	//c)Observer（观察者）：这里指Fighter类。
	//d)ConcreteObserver(具体观察者）：这里指F_Warrior和F_Mage子类。

	//观察者模式的特点：
	//a)在观察者和观察目标之间建立了一个抽象的耦合
	//b)观察目标会向观察者列表中的所有观察者发送通知。
	//c)可以通过增加代码来增加新的观察者或者观察目标，符合开闭原则
	//（3）应用联想
	//a)救援家族成员镖车
	//b)将新闻推荐给符合其胃口的读者
	//c)通过改变自身绘制的图形来真实的反应公司的销售数据。
	//d)炮楼只会对30米内的玩家（列表内玩家）进行攻击。





	/*
	//创建游戏玩家
	_nmsp1::Fighter* pplayerobj1 = new _nmsp1::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
	pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
	_nmsp1::g_playerList.push_back(pplayerobj1); //加入到全局玩家列表中


	_nmsp1::Fighter* pplayerobj2 = new _nmsp1::F_Warrior(20, "李四");
	pplayerobj2->SetFamilyID(100);
	_nmsp1::g_playerList.push_back(pplayerobj2);

	_nmsp1::Fighter* pplayerobj3 = new _nmsp1::F_Mage(30, "王五");
	pplayerobj3->SetFamilyID(100);
	_nmsp1::g_playerList.push_back(pplayerobj3);

	_nmsp1::Fighter* pplayerobj4 = new _nmsp1::F_Mage(50, "赵六");
	pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
	_nmsp1::g_playerList.push_back(pplayerobj4);

	//当某个玩家聊天时，同族人都应该收到该信息
	pplayerobj1->SayWords("全族人立即到沼泽地集结，准备进攻！");

	//释放资源
	delete pplayerobj1;
	delete pplayerobj2;
	delete pplayerobj3;
	delete pplayerobj4;
	*/

	//创建游戏玩家
	_nmsp2::Fighter* pplayerobj1 = new _nmsp2::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
	pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
	
	_nmsp2::Fighter* pplayerobj2 = new _nmsp2::F_Warrior(20, "李四");
	pplayerobj2->SetFamilyID(100);
	
	_nmsp2::Fighter* pplayerobj3 = new _nmsp2::F_Mage(30, "王五");
	pplayerobj3->SetFamilyID(100);
	
	_nmsp2::Fighter* pplayerobj4 = new _nmsp2::F_Mage(50, "赵六");
	pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
	
	//创建通知器
	_nmsp2::Notifier* ptalknotify = new _nmsp2::TalkNotifier();

	//玩家增加到家族列表中来，这样才能收到家族聊天信息
	ptalknotify->addToList(pplayerobj1);
	ptalknotify->addToList(pplayerobj2);
	ptalknotify->addToList(pplayerobj3);
	ptalknotify->addToList(pplayerobj4);

	//某游戏玩家聊天，同族人都应该收到该信息
	pplayerobj1->SayWords("全族人立即到沼泽地集结，准备进攻！", ptalknotify);

	cout << "王五不想再收到家族其他成员的聊天信息了---" << endl;
	ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除
	pplayerobj2->SayWords("请大家听从族长调遣，前往沼泽地！", ptalknotify);

	//释放资源
	delete pplayerobj1;
	delete pplayerobj2;
	delete pplayerobj3;
	delete pplayerobj4;
	delete ptalknotify;



	return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件


